﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using ZackFlame.SimpleBrainFuck.Core.Statments;
using ZackFlame.SimpleBrainFuck.TextResources;

namespace ZackFlame.SimpleBrainFuck.Core
{
    public sealed class SimpleParser
    {
        private string Preprocess(string simpleCode)
        {
            if (simpleCode.Length == 0)
                return string.Empty;

            StringBuilder result = new StringBuilder();
            bool oneLineComment = false;
            bool literal = false;

            int i = 0;
            while (i < simpleCode.Length - 1)
            {
                char ch = simpleCode[i];

                if (!literal && ch == '/' && simpleCode[i + 1] == '/')
                {
                    oneLineComment = true;
                    i++;
                    continue;
                }
                else if (oneLineComment)
                {
                    if (ch == '\n')
                    {
                        oneLineComment = false;
                    }
                    else
                    {
                        i++;
                        continue;
                    }
                }
                else if (ch == '\"' && i != 0 && simpleCode[i - 1] != '\\')
                {
                    literal = !literal;
                }

                result.Append(ch);
                i++;
            }

            if (!oneLineComment)
                result.Append(simpleCode[i]);

            return result.ToString();
        }

        private string[] GetTokens(string line)
        {
            if (line.Length == 0)
                return new string[] { };

            List<string> tokens = new List<string>();
            StringBuilder currentToken = new StringBuilder();

            const int nothing = 0, token = 1, literal = 2;
            string singleTokens = ".=";

            int state = nothing;

            int i = 0;
            while (i < line.Length)
            {
                char ch = line[i];
                bool last = (i == line.Length - 1);

                if ((state == token && (ch == '\"' || ch == '\\')) ||
                    (state == literal && ch == '\\' && last) ||
                    (state == nothing && ch == '\\'))
                {
                    return null;
                }

                if (state == nothing)
                {
                    if (ch == '\"')
                    {
                        state = literal;
                    }
                    else if (singleTokens.Contains(ch))
                    {
                        tokens.Add(ch.ToString());
                    }
                    else if (!char.IsWhiteSpace(ch))
                    {
                        state = token;
                        currentToken.Append(ch);
                    }
                }
                else if (state == token)
                {
                    if (char.IsWhiteSpace(ch))
                    {
                        state = nothing;
                        tokens.Add(currentToken.ToString());
                        currentToken.Length = 0;
                    }
                    else if (singleTokens.Contains(ch))
                    {
                        state = nothing;
                        tokens.Add(currentToken.ToString());
                        tokens.Add(ch.ToString());
                        currentToken.Length = 0;
                    }
                    else
                    {
                        currentToken.Append(ch);
                    }
                }
                else // if state == literal
                {
                    if (ch == '\"')
                    {
                        if (last)
                        {
                            state = nothing;
                            tokens.Add(currentToken.ToString());
                        }
                        else
                        {
                            char next = line[i + 1];
                            if (char.IsWhiteSpace(next) || singleTokens.Contains(next))
                            {
                                state = nothing;
                                tokens.Add(currentToken.ToString());
                                currentToken.Length = 0;
                            }
                            else
                            {
                                return null;
                            }
                        }
                    }
                    else if (ch == '\\')
                    {
                        char unescaped;
                        if (!TryUnescapeChar(line[i + 1], out unescaped))
                            return null;

                        currentToken.Append(unescaped);
                        i++;
                    }
                    else
                    {
                        currentToken.Append(ch);
                    }
                }

                i++;
            }

            if (state == token)
                tokens.Add(currentToken.ToString());
            else if (state == literal)
                return null;

            return tokens.ToArray();
        }

        private bool TryUnescapeChar(char escaped, out char value)
        {
            switch (escaped)
            {
                case 'n':
                    value = '\n';
                    return true;

                case 't':
                    value = '\t';
                    return true;

                case '"':
                    value = '\"';
                    return true;

                case '\\':
                    value = '\\';
                    return true;

                case '0':
                    value = '\0';
                    return true;

                default:
                    value = '\0';
                    return false;
            }
        }

        public static BrainArg ParseParameter(string token)
        {
            if (token.Length > 1 && token[0] == '@')
            {
                var match = BrainMacros.NameRegex.Match(token.Substring(1));

                if (match.Success)
                {
                    return new BrainMacros()
                    {
                        Name = match.Groups[2].Value,
                        Negative = match.Groups[1].Success
                    };
                }
            }

            return new BrainArg() { Value = token };
        }

        public IEnumerable<Statment> BuildSemanticTree(string simpleCode)
        {
            simpleCode = Preprocess(simpleCode.Replace(Environment.NewLine, "\n"));
            string[] lines = simpleCode.Split(new[] { '\n' }, 
                StringSplitOptions.None);

            var body = new LinkedList<Statment>();
            var bodyStack = new Stack<LinkedList<Statment>>();

            for (int i = 0; i < lines.Length; i++)
            {
                string[] tokens = GetTokens(lines[i]);
                if (tokens == null)
                    throw new CodeErrorException(i, CodeErrors.Preprocess_InvalidLine);

                if (tokens.Length > 0)
                {
                    string first = tokens[0];

                    if (first == "end")
                    {
                        if (bodyStack.Count == 0)
                            throw new CodeErrorException(i, CodeErrors.General_SuddenCloseOfStatment);

                        body = bodyStack.Pop();
                    }
                    else if (first == "def")
                    {
                        if (tokens.Length < 2)
                            throw new CodeErrorException(i, CodeErrors.Def_IncorrectDeclaration);

                        BrainDef function = new BrainDef() { Name = tokens[1], Line = i };
                        for (int j = 2; j < tokens.Length; j++)
                        {
                            function.MacrosArgs.Add(
                                new BrainMacros() { Name = tokens[j] });
                        }

                        body.AddLast(function);
                        bodyStack.Push(body);
                        body = function.Statments;
                    }
                    else if (first == "if")
                    {
                        if (tokens.Length != 2)
                            throw new CodeErrorException(i, CodeErrors.If_IncorrectDeclaration);

                        BrainIf brainIf = new BrainIf() { Line = i };
                        brainIf.Parameter = ParseParameter(tokens[1]);

                        body.AddLast(brainIf);
                        bodyStack.Push(body);
                        body = brainIf.IfStatments;
                    }
                    else if (first == "else")
                    {
                        Statment statment = bodyStack.Peek().Last();
                        BrainIf brainIf = statment as BrainIf;
                        if (brainIf == null)
                        {
                            throw new CodeErrorException(i, CodeErrors.If_IfNotDeclarated);
                        }
                        else
                        {
                            body = brainIf.ElseStatments;
                        }
                    }
                    else if (first.StartsWith("$"))
                    {
                        if (first.Length < 2)
                            throw new CodeErrorException(i, CodeErrors.Call_IncorrectCall);

                        string defName = first.Substring(1);
                        BrainCall brainCall = new BrainCall() { DefName = defName, Line = i };
                        for (int j = 1; j < tokens.Length; j++)
                        {
                            brainCall.CallArgs.Add(ParseParameter(tokens[j]));
                        }

                        body.AddLast(brainCall);
                    }
                    else if (first.StartsWith("@"))
                    {
                        if (first.Length < 2 || 
                            tokens.Length < 3 || tokens.Length > 5 ||
                            tokens[1] != "=")
                        {
                            throw new CodeErrorException(i, CodeErrors.Assignment_IncorrectAssignment);
                        }

                        BrainMacros newMacros = (BrainMacros)ParseParameter(tokens[0]);
                        if (newMacros.Negative)
                            throw new CodeErrorException(i, CodeErrors.Assignment_CantBeNegative);

                        var brainExpression = new BrainMacrosExpression();
                        switch (tokens.Length - 2) // tokens after '='
                        {
                            case 1:
                                brainExpression.Left = ParseParameter(tokens[2]);
                                break;

                            case 2:
                                brainExpression.Left = ParseParameter(tokens[3]);
                                brainExpression.Operator = tokens[2];
                                break;

                            case 3:
                                brainExpression.Left = ParseParameter(tokens[2]);
                                brainExpression.Operator = tokens[3];
                                brainExpression.Right = ParseParameter(tokens[4]);
                                break;
                        }

                        body.AddLast(new BrainMacrosAssignment()
                            {
                                Name = newMacros.Name,
                                Expression = brainExpression,
                                Line = i,
                            });
                    }
                    else
                    {
                        BrainPureCode code = new BrainPureCode()
                        { 
                            Value = lines[i].Trim(' ', '\t'),
                            Line = i
                        };
                        body.AddLast(code);
                    }
                }
            }

            if (bodyStack.Count > 0)
                throw new CodeErrorException(lines.Length - 1, CodeErrors.General_UnclosedStatment);

            return body;
        }
    }
}
